/**********************************************************************
*file:W25QXXX驱动文件(标准SPI)
*author:残梦
*versions:V1.0
*date:2025.7.23
*note:
    本文件基于标准SPI编写的W25QXXX驱动
    SPI信号时序定义:时钟=18MHz(上限可以查看数据手册)
    FirstBit=MSB First
    CPOL=0
    CPHA=第一个边沿
    MISO引脚上拉
    CS引脚默认高电平并高速模式
**********************************************************************/
#include "w25qxxx_driver.h"
#include "spi.h"
#include "gpio.h"

//引脚配置区域
#define dW25QXXX_CS(x) GPIOA->BSRR = ((uint32_t)(GPIO_PIN_3) << ((x)?0u:16u))//片选引脚电平设置

typedef enum
{
    eInstruction_ManufacturerDeviceID = 0x90,
    eInstruction_ReadStatusRegister1 = 0x05,
    eInstruction_WriteStatusRegister1 = 0x01,
    eInstruction_WriteEnable = 0x06,
    eInstruction_WriteDisable = 0x04,
    eInstruction_ReadData = 0x03,
    eInstruction_PageProgram = 0x02,
    eInstruction_ChipErase = 0xC7,
    eInstruction_SectorErase4K = 0x20,
    eInstruction_BlockErase32K = 0x52,
    eInstruction_BlockErase64K = 0xD8,
    eInstruction_PowerDown = 0xB9,
    eInstruction_ReleasePowerDown = 0xAB,
}W25QXXX_Instruction_EnumDef;

static W25QXXX_func_Delay W25QXXX_Delay = NULL;

static void SPI_Init(void);
static bool SPI_TransmitData(uint8_t *data,uint16_t Size);
static bool SPI_ReceiveData(uint8_t *data,uint16_t Size);

static uint16_t W25QXXX_ReadUniqueID(void);
static uint8_t W25QXXX_ReadStatusRegister1(void);
static void W25QXXX_WriteStatusRegister1(uint8_t value);
static void W25QXXX_WriteEnable(void);
static void W25QXXX_WriteDisable(void);
static void W25QXXX_WaitBusy(void);
static void W25QXXX_WaitBusy_Delay(uint32_t millisec);
static void W25QXXX_ChipErase(void);
static void W25QXXX_SectorErase(uint32_t address);
bool W25QXXX_PageProgram(uint32_t address,uint8_t *pbuf,uint16_t size);
static void W25QXXX_Write_NoCheck(uint32_t address,uint8_t *pbuf,uint16_t size);

bool SPI_Busy_Tx = false,SPI_Busy_Rx = false;

void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi){SPI_Busy_Tx = false;}
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi){SPI_Busy_Rx = false;}

/****************************************
@function:spi初始化
@param:void
@return:void
@note:
****************************************/
static void SPI_Init(void)
{
    //spi.c中已初始化
}

/****************************************
@function:spi发送数据
@param:data：数据区域
    Size：数据长度
@return:假-失败，真-成功
@note:
****************************************/
static bool SPI_TransmitData(uint8_t *data,uint16_t Size)
{
    if((data == NULL) || (Size == 0))return false;
    while(SPI_Busy_Tx);
    SPI_Busy_Tx = true;
    HAL_StatusTypeDef status = HAL_SPI_Transmit_DMA(&hspi1,(const uint8_t *)data,Size);
    if(status != HAL_OK){dW25QXXX_LOG("Tspi:%d",status); return false;}
    while(HAL_DMA_GetState(hspi1.hdmatx) == HAL_DMA_STATE_BUSY);//等待结束
    return true;
}

/****************************************
@function:spi接收数据
@param:data：数据区域
    Size：数据长度
@return:假-失败，真-成功
@note:
****************************************/
static bool SPI_ReceiveData(uint8_t *data,uint16_t Size)
{
    if((data == NULL) || (Size == 0))return false;
    while(SPI_Busy_Rx);
    SPI_Busy_Rx = true;
    HAL_StatusTypeDef status = HAL_SPI_Receive_DMA(&hspi1,data,Size);
    if(status != HAL_OK){dW25QXXX_LOG("Rspi:%d",status); return false;}
    while(HAL_DMA_GetState(hspi1.hdmarx) == HAL_DMA_STATE_BUSY);//等待结束
    return true;
}

/****************************************
@function:读取芯片ID
@param:void
@return:ID
    0XEF13,表示芯片型号为W25Q80
    0XEF14,表示芯片型号为W25Q16    
    0XEF15,表示芯片型号为W25Q32  
    0XEF16,表示芯片型号为W25Q64 
    0XEF17,表示芯片型号为W25Q128
@note:
****************************************/
static uint16_t W25QXXX_ReadUniqueID(void)
{
    uint8_t cmd[4] = {eInstruction_ManufacturerDeviceID,0x00,0x00,0x00};//0x90=指令,其余为地址
    uint8_t id[2] = {0};

    dW25QXXX_CS(0);
    SPI_TransmitData(cmd,4);
    SPI_ReceiveData(id,2);
    dW25QXXX_CS(1);
    return (((uint16_t )id[0] << 8) | ((uint16_t )(id[1])));
}

/****************************************
@function:读取W25QXXX的状态寄存器1
@param:void
@return:寄存器值
@note:
    bit:7  6   5   4   3   2   1   0
        SPR RV  TB BP2 BP1 BP0 WEL BUSY
    SPR:默认0,状态寄存器保护位,配合WP使用
    TB,BP2,BP1,BP0:FLASH区域写保护设置
    WEL:写使能锁定
    BUSY:忙标记位(1,忙;0,空闲)
    默认:0x00
****************************************/
static uint8_t W25QXXX_ReadStatusRegister1(void)
{
    uint8_t cmd[1] = {eInstruction_ReadStatusRegister1};
    uint8_t value = 0;
    dW25QXXX_CS(0);
    SPI_TransmitData(cmd,1);
    SPI_ReceiveData(&value,1);
    dW25QXXX_CS(1);
    return value;
}

/****************************************
@function:写W25QXXX的状态寄存器1
@param:寄存器值
@return:void
@note:
    bit:7  6   5   4   3   2   1   0
        SPR RV  TB BP2 BP1 BP0 WEL BUSY
    SPR:默认0,状态寄存器保护位,配合WP使用
    TB,BP2,BP1,BP0:FLASH区域写保护设置
    WEL:写使能锁定
    BUSY:忙标记位(1,忙;0,空闲)
    默认:0x00
    只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以写
****************************************/
static void W25QXXX_WriteStatusRegister1(uint8_t value)
{
    uint8_t cmd[2] = {eInstruction_WriteStatusRegister1,value};
    dW25QXXX_CS(0);
    SPI_TransmitData(cmd,2);
    dW25QXXX_CS(1);
}

/****************************************
@function:写使能
@param:void
@return:void
@note:
****************************************/
static void W25QXXX_WriteEnable(void)
{
    uint8_t cmd[1] = {eInstruction_WriteEnable};
    dW25QXXX_CS(0);
    SPI_TransmitData(cmd,1);
    dW25QXXX_CS(1);
}

/****************************************
@function:写禁止
@param:void
@return:void
@note:
****************************************/
static void W25QXXX_WriteDisable(void)
{
    uint8_t cmd[1] = {eInstruction_WriteDisable};
    dW25QXXX_CS(0);
    SPI_TransmitData(cmd,1);
    dW25QXXX_CS(1);
}

/****************************************
@function:等待空闲
@param:void
@return:void
@note:
****************************************/
static void W25QXXX_WaitBusy(void)
{   
	while((W25QXXX_ReadStatusRegister1()&0x01)==0x01);// 等待BUSY位清空
}

/****************************************
@function:等待空闲,带延时
@param:millisec:延时最小时间，单位毫秒
@return:void
@note:
****************************************/
static void W25QXXX_WaitBusy_Delay(uint32_t millisec)
{   
	while((W25QXXX_ReadStatusRegister1()&0x01)==0x01)W25QXXX_Delay(millisec);// 等待BUSY位清空
}

/****************************************
@function:擦除整个芯片
@param:void
@return:void
@note:等待时间超长
****************************************/
static void W25QXXX_ChipErase(void)
{
    uint8_t cmd[1] = {eInstruction_ChipErase};
    W25QXXX_WriteEnable();
    W25QXXX_WaitBusy();
    dW25QXXX_CS(0);
    SPI_TransmitData(cmd,1);
    dW25QXXX_CS(1);
    W25QXXX_WaitBusy_Delay(1);//等待芯片擦除结束
}

/****************************************
@function:擦除一个扇区
@param:address:扇区地址 根据实际容量设置
@return:void
@note:擦除一个扇区的最少时间:150ms
****************************************/
static void W25QXXX_SectorErase(uint32_t address)
{
    address *= 4096;
    uint8_t cmd[4] = {eInstruction_SectorErase4K,(uint8_t )((address>>16)&0xFF),(uint8_t )((address>>8)&0xFF),(uint8_t )(address&0xFF)};
    W25QXXX_WriteEnable();
    W25QXXX_WaitBusy();
    dW25QXXX_CS(0);
    SPI_TransmitData(cmd,4);
    dW25QXXX_CS(1);
    W25QXXX_WaitBusy_Delay(1);//等待芯片擦除结束
}

/****************************************
@function:进入掉电模式
@param:void
@return:void
@note:
****************************************/
void W25QXXX_PowerDown(void)
{
    uint8_t cmd[1] = {eInstruction_PowerDown};
    dW25QXXX_CS(0);
    SPI_TransmitData(cmd,1);
    dW25QXXX_CS(1);
    W25QXXX_Delay(1);
}

/****************************************
@function:退出掉电模式
@param:void
@return:void
@note:
****************************************/
void W25QXXX_ReleasePowerDown(void)
{
    uint8_t cmd[1] = {eInstruction_ReleasePowerDown};
    dW25QXXX_CS(0);
    SPI_TransmitData(cmd,1);
    dW25QXXX_CS(1);
    W25QXXX_Delay(1);
}

/****************************************
@function:读取Flash数据
@param:address:开始读取的地址(24bit)
    pbuf:数据存储区
    size:要读取的字节数(最大65535)
@return:假-失败，真-成功
@note:
****************************************/
bool W25QXXX_ReadData(uint32_t address,uint8_t *pbuf,uint16_t size)
{
    uint8_t cmd[4] = {eInstruction_ReadData,(uint8_t )((address>>16)&0xFF),(uint8_t )((address>>8)&0xFF),(uint8_t )(address&0xFF)};
    bool state = false;

    dW25QXXX_CS(0);
    SPI_TransmitData(cmd,4);
    state = SPI_ReceiveData(pbuf,size);
    dW25QXXX_CS(1);
    return state;
}

/****************************************
@function:在一页(0~65535)内写入少于256个字节的数据
@param:address:开始写入的地址(24bit)
    pbuf:数据存储区
    size:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!	
@return:假-失败，真-成功
@note:
****************************************/
static bool W25QXXX_PageProgram(uint32_t address,uint8_t *pbuf,uint16_t size)
{
    uint8_t cmd[4] = {eInstruction_PageProgram,(uint8_t )((address>>16)&0xFF),(uint8_t )((address>>8)&0xFF),(uint8_t )(address&0xFF)};
    bool state = false;

    W25QXXX_WriteEnable();
    W25QXXX_WaitBusy();
    dW25QXXX_CS(0);
    SPI_TransmitData(cmd,4);
    state = SPI_TransmitData(pbuf,size);
    dW25QXXX_CS(1);
    W25QXXX_WaitBusy();
    return state;
}

/****************************************
@function:无检验写数据(必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!具有自动换页功能)
@param:address:开始写入的地址(24bit)
    pbuf:数据存储区
    size:要写入的字节数(最大65535)
@return:void
@note:在指定地址开始写入指定长度的数据,但是要确保地址不越界
****************************************/
static void W25QXXX_Write_NoCheck(uint32_t address,uint8_t *pbuf,uint16_t size)
{ 			 		 
	uint16_t pageremain = 0;	   
	pageremain=256 - address%256; //单页剩余的字节数
	if(size <= pageremain)pageremain = size;//不大于256个字节
	while(1)
	{
		W25QXXX_PageProgram(address,pbuf,pageremain);
		if(size == pageremain)break;//写入结束了
	 	else //size>pageremain
		{
			pbuf+=pageremain;
			address+=pageremain;
			size-=pageremain;//减去已经写入了的字节数
            pageremain = (size>256)?256:size;//单次写入最大256字节
		}
	}
}

/****************************************
@function:向Flash写数据(该函数带擦除操作)
@param:address:开始写入的地址(24bit)
    pbuf:数据存储区
    size:要写入的字节数(最大65535)
@return:void
@note:
****************************************/ 
static uint8_t W25QXX_buffer[4096] = {0};
void W25QXXX_WriteData(uint32_t address,uint8_t *pbuf,uint16_t size)
{ 
	uint32_t secpos = 0;
	uint16_t secoff = 0,secremain = 0,i = 0;
	uint8_t *W25QXX_BUF = W25QXX_buffer;
 	secpos = address/4096;//扇区地址
	secoff = address%4096;//在扇区内的偏移
	secremain = 4096 - secoff;//扇区剩余空间大小
 	//printf("ad:%X,nb:%X\r\n",address,size);//测试用
 	if(size<=secremain)secremain=size;//不大于4096个字节
	while(1) 
	{
        W25QXXX_ReadData(secpos*4096,W25QXX_BUF,4096);//读出整个扇区的内容
		for(i=0;i<secremain;i++)//校验数据
		{
			if(W25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除  	  
		}
		if(i<secremain)//需要擦除
		{
			W25QXXX_SectorErase(secpos);		//擦除这个扇区
			for(i=0;i<secremain;i++)	   		//复制
			{
				W25QXX_BUF[i+secoff]=pbuf[i];
			}
			W25QXXX_Write_NoCheck(secpos*4096,W25QXX_BUF,4096);//写入整个扇区
		}else W25QXXX_Write_NoCheck(address,pbuf,secremain);//写已经擦除了的,直接写入扇区剩余区间. 				   
		if(size==secremain)break;//写入结束了
		else//写入未结束
		{
			secpos++;//扇区地址增1
			secoff=0;//偏移位置为0 	 
 
		   	pbuf+=secremain;  				//指针偏移
			address+=secremain;				//写地址偏移	   
		   	size-=secremain;			//字节数递减
            secremain = (size>4096)?4096:size;//单次最大写入4096(1个扇区)字节
		}
	} 
}

/****************************************
@function:初始化
@param:id:使用设备ID
@return:假-初始化失败，真-初始化成功
@note:
****************************************/
bool W25QXXX_Init(W25QXXX_DeviceID_EnumDef id,W25QXXX_func_Delay func_Delay)
{
    uint16_t ReadID = 0;
    W25QXXX_Delay = func_Delay;
    SPI_Init();
    ReadID = W25QXXX_ReadUniqueID();
    if(ReadID != id){dW25QXXX_LOG("Non-target device:id=0x%04X\n",ReadID);return false;}
    W25QXXX_ReleasePowerDown();
    return true;
}
